package cn.edu.csust.cvs.management.service.impl;

import cn.edu.csust.cvs.management.common.*;
import cn.edu.csust.cvs.management.controller.request.*;
import cn.edu.csust.cvs.management.exception.ErrorCode;
import cn.edu.csust.cvs.management.repository.*;
import cn.edu.csust.cvs.management.repository.entity.*;
import cn.edu.csust.cvs.management.service.VideoService;
import cn.edu.csust.cvs.management.service.domain.SpeechComposite;
import cn.edu.csust.cvs.management.service.domain.Video;
import cn.edu.csust.cvs.management.service.domain.VideoClip;
import cn.edu.csust.cvs.management.support.FileUploadSupport;
import cn.edu.csust.cvs.management.support.STTSupport;
import cn.edu.csust.cvs.management.support.Speech2TextSupport;
import cn.edu.csust.cvs.management.support.TTSSupport;
import cn.edu.csust.cvs.management.utils.DateUtils;
import cn.edu.csust.cvs.management.utils.FileOptUtils;
import cn.edu.csust.cvs.management.utils.VideoParseUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.*;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Auther: snail
 * @Date: 2021/2/1 00:30
 * @Description: 视频管理服务实现
 */
@Service
@Slf4j
public class VideoServiceImpl implements VideoService {

    @Value("${local.path.video}")
    String videoPath;

    @Value("${local.path.audio}")
    String audioPath;

    @Value("${tencent.tts.capability}")
    Boolean ttsCapability;

    @Value("${tencent.stt.capability}")
    Boolean sttCapability;

    @Autowired
    VideoRepository videoRepository;

    @Autowired
    Speech2TextSupport speech2TextSupport;

    @Autowired
    VideoMd5Repository videoMd5Repository;

    @Autowired
    VideoStorageRepository videoStorageRepository;

    @Autowired
    VideoSegmentationRepository videoSegmentationRepository;

    @Autowired
    VideoClipRepository videoClipRepository;

    @Autowired
    FileUploadSupport fileUploadSupport;

    @Autowired
    SpeechRepository speechRepository;

    @Autowired
    UserStatsRepository userStatsRepository;

    @Autowired
    VideoStatsRepository videoStatsRepository;

    @Autowired
    ClipStatsRepository clipStatsRepository;

    @Autowired
    RecordRepository recordRepository;

    @Autowired
    TTSSupport ttsSupport;

    @Autowired
    STTSupport sttSupport;

    private static final Integer ADMIN = 1;

    final String FFMPEG = "ffmpeg";

    @Override
    public ApiResponse upload(MultipartFile multipartFile) {
        String fileName = multipartFile.getOriginalFilename();
        String filePath = videoPath + fileName;
        File file = new File(filePath);
        try {
            if (file.exists()) {
                org.apache.commons.io.FileUtils.forceDelete(file);
            }
        }catch (IOException e) {
            log.debug("file force delete failed.");
        }
        try {
            multipartFile.transferTo(file);
        }catch (IOException e) {
            e.printStackTrace();
            log.debug("multipartFile transfer to file failed.");
        }
        try {
            String md5 = DigestUtils.md5Hex(new FileInputStream(filePath));
            boolean exist = videoMd5Repository.existsByMd5(md5);
            if (exist){
                return ApiResponse.ok();
            }
            String fileUrl = fileUploadSupport.uploadVideo(fileName,file.getPath());
            long duration = VideoParseUtils.getVideoDuration(file.getAbsolutePath());
            long size = file.length();
            VideoEntity entity = new VideoEntity();
            entity.setCreateTime(LocalDateTime.now());
            entity.setDuration(duration);
            entity.setCreateUser(ADMIN);
            entity.setName(fileName);
            entity.setSize(String.valueOf(size));
            videoRepository.save(entity);
            VideoMd5Entity md5Entity = new VideoMd5Entity();
            md5Entity.setVideoId(entity.getId());
            md5Entity.setMd5(md5);
            videoMd5Repository.save(md5Entity);

            VideoStorageEntity videoStorageEntity = new VideoStorageEntity();
            videoStorageEntity.setVideoId(entity.getId());
            videoStorageEntity.setVideoPath(filePath);
            videoStorageEntity.setVideoUrl(fileUrl);
            videoStorageRepository.save(videoStorageEntity);
            //统计数据入库
            UserStatsEntity statsEntity = userStatsRepository.findByUserId(ADMIN);
            UserStatsEntity userStatsEntity = new UserStatsEntity();
            if (statsEntity == null){
                userStatsEntity.setUserId(ADMIN);
                userStatsEntity.setVideoCount(1);
                userStatsEntity.setTextEditTimes(0);
                userStatsEntity.setVideoEditTimes(0);
            }else{
                BeanUtils.copyProperties(statsEntity,userStatsEntity);
                userStatsEntity.setVideoCount(userStatsEntity.getVideoCount() + 1);
            }
            userStatsRepository.save(userStatsEntity);
            VideoStatsEntity videoStatsEntity = new VideoStatsEntity();
            videoStatsEntity.setVideoId(entity.getId());
            videoStatsEntity.setEditTimes(0);
            videoStatsEntity.setTextCount(0);
            videoStatsRepository.save(videoStatsEntity);
        }catch (IOException e) {
            log.debug("generate file md5 failed.");
        }
        return ApiResponse.ok();
    }

    @Override
    public ApiResponse delete(VideoDeleteRequest request) {
        if (!CollectionUtils.isEmpty(request.getIds())){
            List<Integer> ids = request.getIds();
            List<VideoEntity> entities = new ArrayList<>(20);
            ids.stream().forEach(x->{
                VideoEntity entity = new VideoEntity();
                entity.setId(x);
                entities.add(entity);
            });
            videoRepository.deleteInBatch(entities);
        }
        return ApiResponse.ok();
    }

    @Override
    public ApiResult<Video> detail(Integer id) {

        Optional<VideoEntity> optional = videoRepository.findById(id);
        if (!optional.isPresent()){
            return ApiResult.error(ErrorCode.FILE_DOES_NOT_EXIST);
        }
        VideoEntity videoEntity = optional.get();
        VideoSegmentationEntity segmentationEntity = videoSegmentationRepository.findByVideoId(videoEntity.getId());
        List<VideoClipEntity> clipEntities = videoClipRepository.findByVideoIdOrderByStartTime(videoEntity.getId());
        List<Integer> clipIds = clipEntities.stream().map(VideoClipEntity::getId).
                collect(Collectors.toList());
        List<SpeechEntity> speechEntities = speechRepository.findByClipIdIn(clipIds);
        Map<Integer, String> speechUrlMap = speechEntities.stream().collect(
                Collectors.toMap(SpeechEntity::getClipId, SpeechEntity::getSpeechUrl));
        Map<Integer, Integer> speechIdMap = speechEntities.stream().collect(
                Collectors.toMap(SpeechEntity::getClipId, SpeechEntity::getId));
        clipEntities.stream().forEach(x->{
            x.setSpeechUrl(speechUrlMap.get(x.getId()));
            x.setSpeechId(speechIdMap.get(x.getId()));
        });
        Video video = new Video();
        video.setVideo(videoEntity);
        video.setVideoClips(clipEntities);
        video.setSegmentation(segmentationEntity);
        return ApiResult.ok(video);
    }

    @Override
    public ApiPageResult<List<VideoEntity>> pageQuery(VideoPageQueryRequest request) {
        //规格定义
        Specification<VideoEntity> specification = new Specification<VideoEntity>() {

            /**
             * 构造断言
             * @param root 实体对象引用
             * @param query 规则查询对象
             * @param cb 规则构建对象
             * @return 断言
             */
            @Override
            public Predicate toPredicate(Root<VideoEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>(); //所有的断言
                if(StringUtils.isNotBlank(request.getName())){ //添加断言
                    Predicate likeName = cb.like(root.get("name").as(String.class),"%"+request.getName()+"%");
                    predicates.add(likeName);
                }
                return cb.and(predicates.toArray(new Predicate[0]));
            }
        };
        //分页信息
        Sort sort;
        if (StringUtils.isNotBlank(request.getOrder())){
            sort = Sort.by(Sort.Direction.DESC,request.getOrder());
        }else{
            sort = Sort.by(Sort.Direction.DESC,"createTime");
        }
        Pageable pageable = PageRequest.of(request.getPageNo()-1,request.getPageSize(),sort); //页码：前端从1开始，jpa从0开始，做个转换
        //查询
        Page<VideoEntity> page = this.videoRepository.findAll(specification,pageable);
        List<VideoEntity> videoEntities = page.getContent();
        List<Integer> ids = videoEntities.stream().map(VideoEntity::getId).collect(Collectors.toList());
        List<VideoStorageEntity> storageEntities = this.videoStorageRepository.queryByVideoIdIn(ids);
        Map<Integer, String> urlMap = storageEntities.stream().collect(Collectors.toMap(VideoStorageEntity::getVideoId, VideoStorageEntity::getVideoUrl));
        videoEntities.stream().forEach(x->{
           if (videoSegmentationRepository.existsByVideoId(x.getId())){
               x.setSegmentState(Boolean.TRUE);
           }else{
               x.setSegmentState(Boolean.FALSE);
           }
            x.setFileUrl(urlMap.get(x.getId()));
            //上传作者名称需要修改
            x.setCreateUserName("管理员");
        });
        return ApiPageResult.ok(page.getTotalPages(),page.getTotalElements(),request.getPageSize(), (long) request.getPageNo(),page.getContent());
    }

    @Override
    public ApiPageResult<List<VideoClip>> pageQuery(Integer videoId, PageQueryRequest request) {
        //规格定义
        Specification<VideoClipEntity> specification = new Specification<VideoClipEntity>() {

            /**
             * 构造断言
             * @param root 实体对象引用
             * @param query 规则查询对象
             * @param cb 规则构建对象
             * @return 断言
             */
            @Override
            public Predicate toPredicate(Root<VideoClipEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> predicates = new ArrayList<>(); //所有的断言
                Predicate likeName = cb.equal(root.get("videoId").as(Integer.class),videoId);
                predicates.add(likeName);
                return cb.and(predicates.toArray(new Predicate[0]));
            }
        };
        //分页信息
        Sort sort;
        if (StringUtils.isNotBlank(request.getOrder())){
            sort = Sort.by(Sort.Direction.DESC,request.getOrder());
        }else{
            sort = Sort.by(Sort.Direction.DESC,"startTime");
        }
        Pageable pageable = PageRequest.of(request.getPageNo()-1,request.getPageSize(),sort); //页码：前端从1开始，jpa从0开始，做个转换
        //查询
        Page<VideoClipEntity> page = this.videoClipRepository.findAll(specification,pageable);
        List<VideoClipEntity> videoEntities = page.getContent();
        List<VideoClip> clips = new ArrayList<>(20);
        videoEntities.stream().forEach(x->{
            VideoClip clip = new VideoClip();
            clip.setDuration(x.getDuration());
            clip.setClipPath(x.getClipPath());
            clip.setStartTime(x.getStartTime());
            clip.setName(x.getName());
            clip.setId(x.getId());
            clips.add(clip);
        });
        return ApiPageResult.ok(page.getTotalPages(),page.getTotalElements(),request.getPageSize(), (long) request.getPageNo(),clips);
    }

    @Override
    public ApiResponse segmentByFix(VideoFixSegmentRequest request) {
        List<Integer> ids = request.getIds();
        for (int id:ids) {
            if (videoSegmentationRepository.existsByVideoId(id)){
                return ApiResponse.error(ErrorCode.ALREADY_SEGMENT);
            }
        }
        List<VideoStorageEntity> storageEntities = videoStorageRepository.queryByVideoIdIn(ids);
        storageEntities.stream().forEach(entity->{
            String storagePath = entity.getVideoPath();
            String videoTime = VideoParseUtils.getVideoDurationString(storagePath);
            List<Float> videoData = VideoParseUtils.getVideoData(storagePath);
            List<VideoClip> clips = this.splitByFix(storagePath,videoTime,request.getDuration(),videoData.get(0),videoData.get(1));
            List<VideoClipEntity> clipEntities = new ArrayList<>(20);
            for (VideoClip clip:
                    clips) {
                VideoClipEntity clipEntity = new VideoClipEntity();
                clipEntity.setClipPath(clip.getClipPath());
                clipEntity.setName(clip.getName());
                clipEntity.setDuration(clip.getDuration());
                clipEntity.setStartTime(clip.getStartTime());
                clipEntity.setVideoId(entity.getVideoId());
                clipEntity.setExtractState(Boolean.FALSE);
                String clipUrl = fileUploadSupport.uploadVideo(clip.getName(),clip.getClipPath());
                clipEntity.setClipUrl(clipUrl);
                clipEntities.add(clipEntity);
            }
            videoClipRepository.saveAll(clipEntities);
            //统计信息入库
            List<ClipStatsEntity> clipStatsEntities = new ArrayList<>(20);
            clipEntities.stream().forEach(x->{
                ClipStatsEntity clipStatsEntity = new ClipStatsEntity();
                clipStatsEntity.setClipId(x.getId());
                clipStatsEntity.setEditTimes(0);
                clipStatsEntities.add(clipStatsEntity);
            });
            clipStatsRepository.saveAll(clipStatsEntities);

            VideoSegmentationEntity segmentationEntity = new VideoSegmentationEntity();
            segmentationEntity.setSegmentModel(SegmentModel.FIX);
            segmentationEntity.setVideoId(entity.getVideoId());
            segmentationEntity.setSegmentTime(LocalDateTime.now());
            segmentationEntity.setSegmentUser(ADMIN);
            videoSegmentationRepository.save(segmentationEntity);

            //统计数据入库
            VideoStatsEntity videoStatsEntity = videoStatsRepository.findByVideoId(entity.getVideoId());
            if (videoStatsEntity == null){
                videoStatsEntity = new VideoStatsEntity();
                videoStatsEntity.setVideoId(entity.getVideoId());
                videoStatsEntity.setEditTimes(1);
            }else{
                videoStatsEntity.setEditTimes(videoStatsEntity.getEditTimes() + 1);
            }
            videoStatsEntity.setTextCount(clips.size());
            videoStatsRepository.save(videoStatsEntity);
        });
        //统计数据入库
        UserStatsEntity statsEntity = userStatsRepository.findByUserId(ADMIN);
        UserStatsEntity userStatsEntity = new UserStatsEntity();
        if (statsEntity == null){
            userStatsEntity.setUserId(ADMIN);
            userStatsEntity.setVideoEditTimes(ids.size());
        }else{
            BeanUtils.copyProperties(statsEntity,userStatsEntity);
            userStatsEntity.setVideoEditTimes(userStatsEntity.getVideoEditTimes() + ids.size());
        }
        userStatsRepository.save(userStatsEntity);
        return ApiResponse.ok();
    }

    @Override
    public ApiResponse segmentByFlexible(Integer id, VideoFlexibleSegmentRequest request) {
        if (videoSegmentationRepository.existsByVideoId(id)){
            return ApiResponse.error(ErrorCode.ALREADY_SEGMENT);
        }
        VideoStorageEntity entity = videoStorageRepository.queryByVideoId(id);
        String storagePath = entity.getVideoPath();
        List<Float> videoData = VideoParseUtils.getVideoData(storagePath);
        String videoTime = VideoParseUtils.getVideoDurationString(storagePath);
        List<VideoClip> videoClips = this.splitByFlexible(storagePath,videoTime,request.getStartTime(),request.getEndTime(),videoData.get(1),videoData.get(0));
        List<VideoClipEntity> clipEntities = new ArrayList<>(20);
        videoClips.stream().forEach(x->{
            VideoClipEntity clipEntity = new VideoClipEntity();
            clipEntity.setClipPath(x.getClipPath());
            clipEntity.setName(x.getName());
            clipEntity.setDuration(x.getDuration());
            clipEntity.setStartTime(x.getStartTime());
            clipEntity.setVideoId(entity.getVideoId());
            clipEntity.setExtractState(Boolean.FALSE);
            String clipUrl = fileUploadSupport.uploadVideo(x.getName(),x.getClipPath());
            clipEntity.setClipUrl(clipUrl);
            clipEntities.add(clipEntity);
        });
        videoClipRepository.saveAll(clipEntities);
        VideoSegmentationEntity segmentationEntity = new VideoSegmentationEntity();
        segmentationEntity.setSegmentModel(SegmentModel.FLEXIBLE);
        segmentationEntity.setVideoId(entity.getVideoId());
        segmentationEntity.setSegmentTime(LocalDateTime.now());
        segmentationEntity.setSegmentUser(ADMIN);
        videoSegmentationRepository.save(segmentationEntity);
        List<ClipStatsEntity> statsEntities = new ArrayList<>(20);
        clipEntities.stream().forEach(y->{
            ClipStatsEntity clipStatsEntity = new ClipStatsEntity();
            clipStatsEntity.setClipId(y.getId());
            clipStatsEntity.setEditTimes(0);
            statsEntities.add(clipStatsEntity);
        });
        clipStatsRepository.saveAll(statsEntities);
        VideoStatsEntity videoStatsEntity = videoStatsRepository.findByVideoId(entity.getVideoId());
        if (videoStatsEntity == null){
            videoStatsEntity = new VideoStatsEntity();
            videoStatsEntity.setVideoId(entity.getVideoId());
            videoStatsEntity.setEditTimes(1);
        }else{
            videoStatsEntity.setEditTimes(videoStatsEntity.getEditTimes() + 1);
        }
        videoStatsEntity.setTextCount(clipEntities.size());
        videoStatsRepository.save(videoStatsEntity);

        UserStatsEntity statsEntity = userStatsRepository.findByUserId(ADMIN);
        UserStatsEntity userStatsEntity = new UserStatsEntity();
        if (statsEntity == null){
            userStatsEntity.setUserId(ADMIN);
            userStatsEntity.setVideoEditTimes(1);
        }else{
            BeanUtils.copyProperties(statsEntity,userStatsEntity);
            userStatsEntity.setVideoEditTimes(userStatsEntity.getVideoEditTimes() + 1);
        }
        userStatsRepository.save(userStatsEntity);
        return ApiResponse.ok();
    }

    @Override
    public ApiResponse extract(SpeechExtractRequest request) {
        List<Integer> ids = request.getClipIds();
        List<VideoClipEntity> entities = videoClipRepository.findByIdIn(ids);
        List<SpeechEntity> speechEntities = new ArrayList<>(20);
        entities.stream().forEach(entity->{
            entity.setExtractState(Boolean.TRUE);
            String speechPath =  this.audioExtract(entity.getClipPath());
            String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(speechPath));
            String name = prefix + "." + FilenameUtils.getExtension(speechPath);
            String speechUrl = fileUploadSupport.uploadAudio(name,speechPath);
            SpeechEntity speechEntity = new SpeechEntity();
            speechEntity.setName(name);
            speechEntity.setSpeechPath(speechPath);
            speechEntity.setSpeechUrl(speechUrl);
            speechEntity.setClipId(entity.getId());
            speechEntities.add(speechEntity);
        });
        //保存音频信息
        speechRepository.saveAll(speechEntities);
        //更新音频抽取状态
        videoClipRepository.saveAll(entities);
        return ApiResponse.ok();
    }

    @Override
    public ApiResult<String> stt(Integer speechId) {
        Optional<SpeechEntity> optional = speechRepository.findById(speechId);
        String text = "";
        if (sttCapability){
            text = this.speech2Text(optional.get().getSpeechPath());
            text = removeInvalidChar(text);
        } else {
            text = speech2TextSupport.s2t(optional.get().getSpeechPath());
            text = speech2TextSupport.punctuation(text);
            text = text.replace("\\","");
        }

        return ApiResult.ok(text);
    }

    @Override
    public ApiResponse tts(Integer speechId, String text) {
        Optional<SpeechEntity> optional = speechRepository.findById(speechId);
        SpeechEntity entity = optional.get();
        String speechPath = "";
        if (ttsCapability){
            speechPath = this.text2Speech(text,entity.getSpeechPath());
        } else {
            List<String> paths = speech2TextSupport.t2s(text);
            speechPath = this.mergeAudios(paths);
            //提高音量，降低语速
            speechPath = this.auxiliaryProcess(speechPath);
        }
        File file = new File(speechPath);
        String speechUrl = fileUploadSupport.uploadAudio(file.getName(),speechPath);
        entity.setSpeechPath(speechPath);
        entity.setSpeechUrl(speechUrl);
        speechRepository.save(entity);
        //统计数据入库
        UserStatsEntity statsEntity = userStatsRepository.findByUserId(ADMIN);
        UserStatsEntity userStatsEntity = new UserStatsEntity();
        if (statsEntity == null){
            userStatsEntity.setUserId(ADMIN);
            userStatsEntity.setTextEditTimes(1);
        }else{
            BeanUtils.copyProperties(statsEntity,userStatsEntity);
            userStatsEntity.setTextEditTimes(userStatsEntity.getTextEditTimes() + 1);
        }
        userStatsRepository.save(userStatsEntity);
        return ApiResponse.ok();
    }

    @Override
    public ApiResponse composite(SpeechCompositeRequest request) {
        List<Integer> clipIds = request.getClipIds();
        List<VideoClipEntity> entities = videoClipRepository.findByIdIn(clipIds);
        Map<Integer, String> clipMap = entities.stream().collect(Collectors.toMap(VideoClipEntity::getId, VideoClipEntity::getClipPath));
        List<SpeechEntity> speechEntities = speechRepository.findByClipIdIn(clipIds);
        Map<Integer, String> speechMap = speechEntities.stream().collect(Collectors.toMap(SpeechEntity::getClipId, SpeechEntity::getSpeechPath));
        List<SpeechComposite> composites = new ArrayList<>(20);
        clipIds.stream().forEach(x->{
            String clipPath = clipMap.get(x);
            String speechPath = speechMap.get(x);
            SpeechComposite speechComposite = new SpeechComposite();
            speechComposite.setClipId(x);
            speechComposite.setAudioPath(speechPath);
            speechComposite.setVideoPath(clipPath);
            composites.add(speechComposite);
        });
        for (SpeechEntity speechEntity: speechEntities) {
            int aDuration = VideoParseUtils.getAudioDuration(speechEntity.getSpeechPath());
            String clipPath = clipMap.get(speechEntity.getClipId());
            int vDuration = VideoParseUtils.getVideoDuration(clipPath);
            if (aDuration > vDuration){
                return ApiResponse.error(ErrorCode.AUDIO_VIDEO_DURATION_INCONSISTENT);
            }
        }
        this.speechComposite(composites);
        Map<Integer, String> pathMap = composites.stream().collect(Collectors.toMap(SpeechComposite::getClipId, SpeechComposite::getVideoPath));
        entities.stream().forEach(entity->{
            File file = new File(pathMap.get(entity.getId()));
            String clipUrl = fileUploadSupport.uploadVideo(file.getName(),pathMap.get(entity.getId()));
            entity.setClipUrl(clipUrl);
            entity.setClipPath(pathMap.get(entity.getId()));
        });
        videoClipRepository.saveAll(entities);
        //统计信息入库
        List<ClipStatsEntity> statsEntities = new ArrayList<>(20);
        clipIds.stream().forEach(x->{
            ClipStatsEntity entity = clipStatsRepository.findByClipId(x);
            if (entity == null){
                entity = new ClipStatsEntity();
                entity.setEditTimes(1);
                entity.setClipId(x);
            }else{
                entity.setEditTimes(entity.getEditTimes() + 1);
            }
            statsEntities.add(entity);
        });
        clipStatsRepository.saveAll(statsEntities);
        return ApiResponse.ok();
    }

    @Override
    public ApiResponse composite(RecordCompositeRequest request) {

        Optional<RecordEntity> optional = this.recordRepository.findById(request.getRecordId());
        RecordEntity entity = optional.get();
        String audioPath = entity.getFilePath();
        Optional<VideoClipEntity> opt = this.videoClipRepository.findById(request.getClipId());
        VideoClipEntity clipEntity = opt.get();
        SpeechEntity speechEntity = this.speechRepository.findByClipId(clipEntity.getId());
        speechEntity.setSpeechPath(entity.getFilePath());
        speechEntity.setSpeechUrl(entity.getFileUrl());
        this.speechRepository.save(speechEntity);

        String videoPath = clipEntity.getClipPath();

        //时长要一致
        int aDuration = VideoParseUtils.getAudioDuration(audioPath);
        int vDuration = VideoParseUtils.getVideoDuration(videoPath);
        if (aDuration > vDuration){
            return ApiResponse.error(ErrorCode.AUDIO_VIDEO_DURATION_INCONSISTENT);
        }
        List<SpeechComposite> composites = new ArrayList<>(1);
        SpeechComposite composite = new SpeechComposite();
        composite.setClipId(request.getClipId());
        composite.setVideoPath(videoPath);
        composite.setAudioPath(audioPath);
        composites.add(composite);
        this.speechComposite(composites);
        String clipPath = composites.get(0).getVideoPath();
        clipEntity.setClipPath(clipPath);
        File file = new File(clipPath);
        String clipUrl = fileUploadSupport.uploadVideo(file.getName(),clipPath);
        clipEntity.setClipUrl(clipUrl);
        this.videoClipRepository.save(clipEntity);
        return ApiResponse.ok();
    }

    @Override
    public ApiResponse merge(Integer videoId) {
        List<VideoClipEntity>  entities = videoClipRepository.findByVideoIdOrderByStartTime(videoId);
        List<String> clipPaths = new ArrayList<>(20);
        entities.stream().forEach(entity->{
            clipPaths.add(entity.getClipPath());
        });
        VideoStorageEntity storageEntity = videoStorageRepository.queryByVideoId(videoId);
        String newPath = this.merge(clipPaths,storageEntity.getVideoPath());
        File file = new File(newPath);
        String fileUrl = fileUploadSupport.uploadVideo(file.getName(),newPath);
        VideoStorageEntity entity = videoStorageRepository.queryByVideoId(videoId);
        entity.setVideoUrl(fileUrl);
        entity.setVideoPath(newPath);
        videoStorageRepository.save(entity);
        //清除视频片段数据
        videoSegmentationRepository.deleteByVideoId(videoId);
        //清除切分状态数据
        videoClipRepository.deleteByVideoId(videoId);
        return ApiResponse.ok();
    }

    /**
     * 固定切分核心方法
     * @param fileName
     * @param videoTime
     * @param duration
     * @param videoFps
     */
    private List<VideoClip> splitByFix(String fileName,String videoTime, int duration, float videoRate, float videoFps) {

        File file = FileOptUtils.createFileBySysDate(videoPath);
        if (!file.exists()) {
            return Collections.emptyList();
        }
        String path = file.getPath() + File.separator;
        int size = DateUtils.convert(videoTime) / duration;
        int remain = DateUtils.convert(videoTime) % duration;
        String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(fileName));
        List<VideoClip> clips = new ArrayList<>(20);
        for (int i = 0; i < size; i++) {
            String name = prefix + "_" + i + "." + FilenameUtils.getExtension(fileName);
            String startTime = DateUtils.format((long) i * duration);
            float startFrame = i * videoFps * duration;
            float endFrame = (i+1) * videoFps * duration;
            if (i == size - 1){
                endFrame += remain * videoFps;
                duration += remain;
            }
            String command = FFMPEG + " -i " + fileName + " -b:v " + videoRate + "k -vf select=between(n\\,"+ startFrame + "\\," + endFrame + ") -ss " + startTime + " -t " + duration + " " + path + name + " -y";
            log.info("command:"+command);
            run(command);
            VideoClip clip = new VideoClip();
            clip.setName(name);
            clip.setDuration(duration);
            clip.setStartTime(startTime);
            clip.setClipPath(path + name);
            clips.add(clip);
        }
        return clips;
    }

    /**
     * 自由时长切分核心方法
     * @param fileName
     * @param startTime
     * @param endTime
     * @param videoFps
     * @param videoRate
     * @return
     */
    private List<VideoClip> splitByFlexible(String fileName, String videoTime, String startTime, String endTime, Float videoFps, Float videoRate) {
        if ("00:00:00".equals(startTime)){
            return splitByFlexible4two(fileName,videoTime,endTime,videoFps,videoRate);
        }else{
            return splitByFlexible4three(fileName,videoTime,startTime,endTime,videoFps,videoRate);
        }
    }

    /**
     * 两段式切分
     * @param fileName
     * @param videoTime
     * @param endTime
     * @param videoFps
     * @param videoRate
     * @return
     */
    private List<VideoClip> splitByFlexible4two(String fileName, String videoTime, String endTime, Float videoFps, Float videoRate){
        File file = FileOptUtils.createFileBySysDate(videoPath);
        if (!file.exists()) {
            return Collections.emptyList();
        }
        String path = file.getPath() + File.separator;
        int st = 0;
        int et = DateUtils.convert(endTime);
        float endFrame = videoFps * et;
        String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(fileName));
        List<VideoClip> clips = new ArrayList<>(2);
        try {
            String name = prefix + "_0." + FilenameUtils.getExtension(fileName);
            String command = FFMPEG  + " -i " + fileName +  " -b:v " + videoRate + "k -vf select=between(n\\,"+ 0 + "\\," + endFrame + ") -ss 00:00:00 -t " + et + " " + path + name + " -y";
            log.info("command:"+command);
            run(command);
            VideoClip clip = new VideoClip();
            clip.setName(name);
            clip.setClipPath(path + name);
            clip.setStartTime("00:00:00");
            clip.setDuration(et - st);
            clips.add(clip);
        } catch (Exception e) {
            log.error("split by flexible failed.");
        }
        float startFrame = videoFps * et;
        int tt = DateUtils.convert(videoTime);
        endFrame  = videoFps * tt;
        String startTime = endTime;
        try {
            String name = prefix + "_1." + FilenameUtils.getExtension(fileName);
            String command = FFMPEG + " -i " + fileName +  " -b:v " + videoRate + "k -vf select=between(n\\,"+ startFrame + "\\," + endFrame + ") -ss " + startTime + " -t " + (tt - et) + " " + path + name + " -y";
            log.info("command:"+command);
            run(command);
            VideoClip clip = new VideoClip();
            clip.setName(name);
            clip.setClipPath(path + name);
            clip.setStartTime(endTime);
            clip.setDuration(tt - et);
            clips.add(clip);
        } catch (Exception e) {
            log.error("split by flexible failed.");
        }
        return clips;
    }

    /**
     * 三段式切分
     * @param fileName
     * @param videoTime
     * @param startTime
     * @param endTime
     * @param videoFps
     * @param videoRate
     * @return
     */
    private List<VideoClip> splitByFlexible4three(String fileName, String videoTime, String startTime, String endTime, Float videoFps, Float videoRate){
        File file = FileOptUtils.createFileBySysDate(videoPath);
        if (!file.exists()) {
            return Collections.emptyList();
        }
        String path = file.getPath() + File.separator;
        int st = DateUtils.convert(startTime);
        float startFrame = 0;
        float endFrame = videoFps * st;
        String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(fileName));
        List<VideoClip> clips = new ArrayList<>(2);
        try {
            String name = prefix + "_0." + FilenameUtils.getExtension(fileName);
            String command = FFMPEG +" -i " + fileName +  " -b:v " + videoRate + "k -vf select=between(n\\,"+ startFrame + "\\," + endFrame + ") -ss 00:00:00 -t " + st + " " + path + name + " -y";
            log.info("command:"+command);
            run(command);
            VideoClip clip = new VideoClip();
            clip.setName(name);
            clip.setClipPath(path + name);
            clip.setStartTime("00:00:00");
            clip.setDuration(st);
            clips.add(clip);
        } catch (Exception e) {
            log.error("split by flexible _0 failed.");
        }
        startFrame = videoFps * st;
        int et = DateUtils.convert(endTime);
        endFrame  = videoFps * et;
        try {
            String name = prefix + "_1." + FilenameUtils.getExtension(fileName);
            String command = FFMPEG + " -i " + fileName +  " -b:v " + videoRate + "k -vf select=between(n\\,"+ startFrame + "\\," + endFrame + ") -ss " + startTime + " -t " + (et - st) + " " + path + name + " -y";
            log.info("command:"+command);
            run(command);
            VideoClip clip = new VideoClip();
            clip.setName(name);
            clip.setClipPath(path + name);
            clip.setStartTime(startTime);
            clip.setDuration(et - st);
            clips.add(clip);
        } catch (Exception e) {
            log.error("split by flexible failed.");
        }
        startFrame = videoFps * et;
        int tt = DateUtils.convert(videoTime);
        endFrame  = videoFps * tt;
        startTime = endTime;
        try {
            String name = prefix + "_2." + FilenameUtils.getExtension(fileName);
            String command = FFMPEG + " -i " + fileName +  " -b:v " + videoRate + "k -vf select=between(n\\,"+ startFrame + "\\," + endFrame + ") -ss " + startTime + " -t " + (tt - et) + " " + path + name + " -y";
            log.info("command:"+command);
            run(command);
            VideoClip clip = new VideoClip();
            clip.setName(name);
            clip.setClipPath(path + name);
            clip.setStartTime(startTime);
            clip.setDuration(tt - et);
            clips.add(clip);
        } catch (Exception e) {
            log.error("split by flexible failed.");
        }
        return clips;
    }

    /**
     * 音频提取核心方法
     * @param fileName
     * @return
     */
    private String audioExtract(String fileName) {
        File file = FileOptUtils.createFileBySysDate(audioPath);
        String audioPath = file.getPath() + File.separator;
        String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(fileName));
        String name = prefix + "." + "wav";
        try {
            String command = FFMPEG + " -i " + fileName + " -ac 1 -ar 16000 -ab 16 " + audioPath + name + " -y";
            log.info("command:"+command);
            run(command);
        } catch  (Exception e) {
            log.error("audio extract failed.");
        }
        return audioPath + name;
    }

    /**
     * 语音转文本
     * @param speechPath
     * @return
     */
    private String speech2Text(String speechPath)  {
       long taskId = sttSupport.stt(speechPath);
       String text = "";
       int count = 0;
       try {
           while(StringUtils.isBlank(text) && count < 10){
               text = sttSupport.queryResult(taskId);
               log.info("第"+count+"次查询:"+text);
               Thread.sleep(1000L);
               count++;
           }
       } catch (InterruptedException e) {
            e.printStackTrace();
       }
       return text;
    }

    /**
     * 文本转语音
     * @param text
     * @return
     */
    private String text2Speech(String text,String filePath){

        return ttsSupport.tts(text,filePath);
    }

    /**
     * 语音合成
     * @param composites
     */
    private void speechComposite(List<SpeechComposite> composites) {
        if (CollectionUtils.isEmpty(composites)){
            return;
        }
        for (SpeechComposite composite:
                composites) {
            String videoPath = composite.getVideoPath();
            String audioPath = composite.getAudioPath();
            String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(videoPath));
            String ext = FilenameUtils.getExtension(videoPath);
            String fileName = prefix + "_." + ext;
            String finalName = prefix + "_final." + ext;
            String output = FilenameUtils.getFullPath(videoPath);
            String filePath = output + fileName;
            String finalFilePath = output + finalName;
            composite.setVideoPath(finalFilePath);
            // 执行分割命令
            try {
                // 创建命令
                String command = FFMPEG + " -i " + videoPath + " -i " + audioPath + "  -c:v copy -map 0:v -map 1:a  " + filePath + " -y";
                log.info("command:"+command);
                run(command);
                command = FFMPEG + " -i " + filePath + " -ac 2 -ar 44100 -vcodec copy " + finalFilePath;
                log.info("command:"+command);
                run(command);
            } catch (Exception e) {
                log.error("speech composite failed.");
            }
        }
    }

    /**
     * 合并视频片段
     * @param fileName
     * @param storagePath
     */
    private String merge(List<String> fileName, String storagePath){

        String command;// 执行的命令
        String data = "";
        int count = fileName.size();// 分割为几段
        // 获取待合并视频名称
        for (int i = 0; i < count; i++){
            try {
                data += "file \'";
                data += fileName.get(i);
                data += "\'\r";
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        String prefix = FilenameUtils.getName(FilenameUtils.getBaseName(storagePath));
        String ext = FilenameUtils.getExtension(storagePath);
        String finalName = videoPath + prefix + "_final." + ext;
        try {
            File mergefile = FileOptUtils.createMergeFile(videoPath, data);
            if (mergefile == null) {
                return null;
            }
            // 创建命令
            command = FFMPEG + " -f concat -safe 0 -i " +mergefile.getPath() +" -c copy " + finalName + " -y";
            log.info("command:"+command);
            run(command);
            return finalName;
        } catch (Exception e) {
            log.error("clip merge failed.");
        }
        return null;
    }

    /**
     * 系统运行指令
     * @param command
     */

    /**
     * 系统运行指令
     * @param command
     */
    private void run(String command){
        Process p = null;
        try {
            Runtime runtime = Runtime.getRuntime();
            p = runtime.exec(command);
            final InputStream is1 = p.getInputStream();
            final InputStream is2 = p.getErrorStream();
            new Thread() {
                public void run() {
                    BufferedReader br1 = new BufferedReader(new InputStreamReader(is1));
                    try {
                        String line1 = null;
                        while ((line1 = br1.readLine()) != null) {
                            if (line1 != null){
                                System.out.println(line1);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    finally{
                        try {
                            is1.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
            new Thread() {
                public void  run() {
                    BufferedReader br2 = new  BufferedReader(new  InputStreamReader(is2));
                    try {
                        String line2 = null ;
                        while ((line2 = br2.readLine()) !=  null ) {
                            if (line2 != null){
                                System.out.println(line2);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    finally{
                        try {
                            is2.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
            p.waitFor();
            p.destroy();
        } catch (Exception e) {
            try{
                p.getErrorStream().close();
                p.getInputStream().close();
                p.getOutputStream().close();
            }
            catch(Exception ee){}
        }
    }

    /**
     * 语音合并
     * @param paths
     * @return
     */
    private String mergeAudios(List<String> paths){
        String command;// 执行的命令
        String data = "";
        int count = paths.size();// 分割为几段
        if (count == 1){
            return paths.get(0);
        }
        // 获取待合并视频名称
        for (int i = 0; i < count; i++){
            try {
                data += "file \'";
                data += paths.get(i);
                data += "\'\r";
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
            File mergefile = FileOptUtils.createMergeFile(audioPath, data);
            if (mergefile == null) {
                return null;
            }
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
            String prefix = LocalDateTime.now(ZoneOffset.of("+8")).format(formatter);
            // 创建命令
            String targetFile = audioPath + prefix + ".wav";
            command = "ffmpeg -f concat -safe 0 -i " +mergefile.getPath() +" -c copy " + targetFile + " -y";
            log.info("command:"+command);
            run(command);
            return targetFile;
        } catch (Exception e) {
            log.error("audio merge failed.");
        }
        return null;
    }

    /**
     * 辅助处理
     * @param filePath
     * @return
     */
    private String auxiliaryProcess(String filePath){
        String command;// 执行的命令
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
            String prefix = LocalDateTime.now(ZoneOffset.of("+8")).format(formatter);
            // 创建命令
            String targetFile = audioPath + prefix + "_ap.wav";
            command = "ffmpeg -i " + filePath + " -filter:a volume=2,atempo=0.8" + " " + targetFile;
            log.info("command:"+command);
            run(command);
            return targetFile;
        } catch (Exception e) {
            log.error("audio auxiliary process failed.");
        }
        return null;
    }

    /**
     * 去掉无效字符
     * @param text
     * @return
     */
    private String removeInvalidChar(String text){
        int i;
        while(text.contains("[")){
            int startleft = text.indexOf("[");
            i = startleft;
            while (text.charAt(i) != ']'){
                i++;
            }
            String timestamp = text.substring(startleft,++i);
            text = text.replace(timestamp,"");
        }
        if (StringUtils.isNotBlank(text)) {
            Pattern p = Pattern.compile("\\s*|\t|\r|\n");
            Matcher m = p.matcher(text);
            text = m.replaceAll("");
            text = text.replaceAll(" ", "");
        }
        return text;
    }
}
